﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Instance;
using Saxon.Api;
using WMM.Tapioca.Utilities;
using System.Collections;
using System.Xml.Linq;

namespace WMM.Tapioca.Formula
{
    /// <summary>
    /// The base class of ValueAssertion and ExistenceAssertion.
    /// </summary>
    public abstract class VariableSetAssertion : VariableSet
    {
        List<Arc> assertionToMessageArcs;

        /// <returns> List of arcs Assertion to Message. </returns>
        public List<Arc> AssertionToMessageArcs
        {
            get { return assertionToMessageArcs; }
            set { assertionToMessageArcs = value; }
        }

        private string elementLabel;

        /// <returns> The Element Label. </returns>
        public string ElementLabel
        {
            get { return elementLabel; }
            set { elementLabel = value; }
        }

        private Message messageSatisfied;

        /// <summary>
        /// Arcrole identifying that the message is associated with assertion success.
        /// arcrole="http://xbrl.org/arcrole/2010/assertion-satisfied-message".
        /// </summary>
        public Message MessageSatisfied
        {
            get { return messageSatisfied; }
            set { messageSatisfied = value; }
        }

        private Message messageUnsatisfied;

        /// <summary>
        /// Arcrole identifying that the message is associated with assertion unsuccessful.
        /// arcrole="http://xbrl.org/arcrole/2010/assertion-unsatisfied-message".
        /// </summary>
        public Message MessageUnsatisfied
        {
            get { return messageUnsatisfied; }
            set { messageUnsatisfied = value; }
        }        

        protected string test;

        /// <returns> The value assertion's test expression. </returns>
        public string Test
        {
            get { return test; }
            set { test = value; }
        }               

        /// <summary>
        /// Constructor.
        /// </summary>
        public VariableSetAssertion(Resource resource, Dictionary<string, string> namespaces)
            : base(resource, namespaces)
        {
            assertionToMessageArcs = new List<Arc>();
        }

        public VariableSetAssertion()
        {
        }

        /// <summary>
        /// Evaluating the assertion.
        /// </summary>
        public abstract bool EvaluateAssertion(InstanceDocument instance, FormulaProcessor formulaProcessor);


        internal void EvaluateFactVariables(Dictionary<string, List<FactItem>> variableItems, Dictionary<string, List<List<FactItem>>> variableSequences, Dictionary<string, XdmAtomicValue> variableFallBack)
        {
            List<FactItem> itemsToFilter = GetItems(dts.Instance.Facts);

            foreach (Filter filter in this.GroupFilters)
            {
                itemsToFilter = filter.EvaluateFilter(this, itemsToFilter, dts);
            }

            AddVertices(dependencyGraph);

            AddEdges(dependencyGraph);

            Queue<Vertex> q = dependencyGraph.GetVerticesInDegreeZero();

            List<Vertex> evaluationOrderVariable = new List<Vertex>();
            List<Vertex> unresolved = new List<Vertex>();

            foreach (Vertex vertex in q)
            {
                dependencyGraph.DependencyResolve(vertex, ref evaluationOrderVariable, ref unresolved);
            }

            foreach (Vertex vertex in evaluationOrderVariable)
            {
                //FactVariable factVariable = this.GetFactVariable(vertex.name, this);

                Variable variable = this.GetVariable(vertex.name, this);

                if (variable is FactVariable)
                {
                    FactVariable factVariable = variable as FactVariable;

                    List<FactItem> factVariableItems = this.FilteringFacts(this, factVariable, itemsToFilter);

                    if (!factVariable.BindAsSequence)
                    {
                        if (factVariableItems.Count != 0)
                        {
                            this.variableItems.Add(vertex.name, factVariableItems);
                        }
                        else
                        {
                            if (factVariable.FallbackString != null)
                            {
                                variableFallBack.Add(vertex.name, new XdmAtomicValue(factVariable.FallbackValue));
                            }
                        }
                    }
                    else
                    {
                        if (this.sequencesConceptRelationFilter.Count == 0)
                        {
                            List<List<FactItem>> sequences = new List<List<FactItem>>();

                            EvaluateSourceSequence(factVariable, factVariableItems, sequences);

                            this.variableSequences.Add(vertex.name, sequences);
                        }
                        else
                        {
                            this.variableSequences.Add(vertex.name, this.sequencesConceptRelationFilter);
                        }
                    }
                }
                else if (variable is GeneralVariable)
                {
                    List<string> dependecyVariables = GetDependecyVariables(this, vertex.name);

                    string result = EvaluateExpression(dependecyVariables, this, (variable as GeneralVariable).Select);
                }
            }            
        }


        private void EvaluateSourceSequence(FactVariable factVariable, List<FactItem> factVariableItems, List<List<FactItem>> sequences)
        {
            bool isAllAspectsCovered;

            if (this.aspectsCoveredInFilter.TryGetValue("all", out isAllAspectsCovered))
            {
                sequences.Add(factVariableItems);
                return;
            }

            List<FactItem> temp = new List<FactItem>(factVariableItems);

            foreach (FactItem item in factVariableItems)
            {
                List<FactItem> sequence = new List<FactItem>();

                sequence.Add(item);
                temp.Remove(item);

                foreach (FactItem itemToCompare in factVariableItems)
                {
                    if (item != itemToCompare)
                    {
                        if (MatchUncoveredAspect(item, itemToCompare, this, factVariable, factVariable, factVariableItems))
                        {
                            sequence.Add(itemToCompare);
                            temp.Remove(itemToCompare);
                        }
                    }
                }

                sequences.Add(sequence);
                break;
            }

            if (temp.Count > 0)
            {
                EvaluateSourceSequence(factVariable, temp, sequences);
            }
        }


        protected void BindVariable(Processor processor, XPathSelector selector, List<FactItem> cartesianProductItem, List<string> variableNames, Dictionary<string, List<FactItem>> variableAsSequenceEntries, Dictionary<string, FactItem> variableEntries, Dictionary<string, XdmAtomicValue> variableFallBack)
        {
            // bindAsSequence = false

            for (int i = 0; i < cartesianProductItem.Count; i++)
            {
                FactItem item = cartesianProductItem[i];

                string variableName = variableNames[i];
                string prefix = variableName.GetPrefix();  // Item1 = name of variable

                //XdmNode xdmNode = processor.NewDocumentBuilder().Build(item.FactElement.ToXmlNode());
                //IEnumerator enumerator = xdmNode.EnumerateAxis(XdmAxis.Child);

                //XdmNode elementNode = (XdmNode)enumerator.Current;

                //if (prefix.Length > 0)
                //{
                //    selector.SetVariable(new QName(namespaces[prefix], variableName), elementNode);
                //}
                //else
                //{
                //    selector.SetVariable(new QName(variableName), elementNode);
                //}

                double value;

                if (prefix.Length > 0)
                {
                    if (double.TryParse(item.Value, out value))
                    {
                        selector.SetVariable(new QName(namespaces[prefix], variableName), new XdmAtomicValue(value));
                    }
                    else
                    {
                        selector.SetVariable(new QName(namespaces[prefix], variableName), new XdmAtomicValue(item.Value));
                    }
                }
                else
                {
                    if (double.TryParse(item.Value, out value))
                    {
                        selector.SetVariable(new QName(variableName), new XdmAtomicValue(value));
                    }
                    else
                    {
                        selector.SetVariable(new QName(variableName), new XdmAtomicValue(item.Value));
                    }
                }

                log.Debug("Formula trace: Fact Variable " + variableName + ": bound value fact(" + item.Name + ", " + item.ContextRefName + ", " + item.UnitRefName + ", '" + item.Value + "')");
                variableEntries.Add(variableName, item);
            }

            // Fallback variables

            BindVariableFallback(selector, variableFallBack);

            // bindAsSequence = true

            BindVariableSequence(processor, selector, variableAsSequenceEntries);

            foreach (VariableSet variableSet in this.VariableSetArcs)
            {
                if (variableSet.Variable.Name.LocalName.Equals("generalVariable"))
                {
                    GeneralVariable generalVariable = this.GetGeneralVariable(this, variableSet);

                    string prefix = variableSet.Name.GetPrefix();

                    XPathSelector selector2 = compiler.Compile(generalVariable.Select).Load();

                    BindVariableValue(variableEntries, parameterDictionary, selector2, variableAsSequenceEntries);

                    selector2.ContextItem = selector.ContextItem;

                    if (prefix.Length > 0)
                    {
                        selector2.SetVariable(new QName(namespaces[prefix], variableSet.Name), new XdmAtomicValue(0));
                    }
                    else
                    {
                        selector2.SetVariable(new QName(variableSet.Name), new XdmAtomicValue(0));
                    }

                    XdmValue xdmValue = selector2.Evaluate();

                    if (prefix.Length > 0)
                    {
                        selector.SetVariable(new QName(namespaces[prefix], variableSet.Name), xdmValue);
                    }
                    else
                    {
                        selector.SetVariable(new QName(variableSet.Name), xdmValue);
                    }
                }
            }
        }        


        protected bool EvaluatePrecondition(XPathCompiler compiler, Dictionary<string, FactItem> variableEntries, Dictionary<string, List<FactItem>> variableAsSequenceEntries, Dictionary<string, XdmAtomicValue> variableFallBack)
        {
            XPathSelector selectorPrecondition = null;
            Boolean precondition = true;

            if (this.Precondition != null)
            {
                string preconditionTest = this.Precondition.Test;

                selectorPrecondition = compiler.Compile(preconditionTest).Load();

                foreach (string variableName in variableEntries.Keys)
                {
                    selectorPrecondition.SetVariable(new QName(nsvariable, variableName), new XdmAtomicValue(variableEntries[variableName].Value));
                }

                foreach (string variableName in variableFallBack.Keys)
                {
                    string prefix = variableName.GetPrefix();

                    if (prefix.Length > 0)
                    {
                        selectorPrecondition.SetVariable(new QName(namespaces[prefix], variableName), variableFallBack[variableName]);
                    }
                    else
                    {
                        selectorPrecondition.SetVariable(new QName(variableName), variableFallBack[variableName]);
                    }
                }

                // bindAsSequence = true

                foreach (string sequenceName in variableAsSequenceEntries.Keys)
                {
                    List<FactItem> currentSequence = variableAsSequenceEntries[sequenceName];

                    string prefix = sequenceName.GetPrefix();  // Item1 = name of variable

                    ArrayList list = new ArrayList();

                    foreach (FactItem item in currentSequence)
                    {
                        XdmNode itemXdm = processor.NewDocumentBuilder().Build(item.FactElement.ToXmlNode());
                        list.Add(itemXdm);
                    }

                    if (prefix.Length > 0)
                    {
                        selectorPrecondition.SetVariable(new QName(namespaces[prefix], sequenceName), new XdmValue(list));
                    }
                    else
                    {
                        selectorPrecondition.SetVariable(new QName(sequenceName), new XdmValue(list));
                    }

                    log.Info("Formula trace: Fact Variable " + sequenceName + ": bound as sequence");
                }

                precondition = Boolean.Parse(selectorPrecondition.EvaluateSingle().ToString());
            }

            return precondition;
        }


        protected void BuildMessage(FormulaProcessor formulaProcessor, XPathCompiler compiler, Dictionary<string, string> parameterDictionary, Dictionary<string, FactItem> variableEntries, bool evaluateResult, Dictionary<string, XdmAtomicValue> variableFallBack)
        {
            if (this.ElementLabel == null)
            {
                foreach (Arc assertionToMessageArc in this.AssertionToMessageArcs)
                {
                    if (assertionToMessageArc.ArcRole.Equals("http://xbrl.org/arcrole/2010/assertion-satisfied-message"))
                    {
                        if (evaluateResult)
                        {
                            string message = CompileMessage(this.MessageSatisfied, compiler, variableEntries, parameterDictionary, variableFallBack);
                            log4net.GlobalContext.Properties["satisfied"] = "yes";
                            FormulaProcessor.log.Info(message);
                            formulaProcessor.assertionsSatisfied++;
                        }
                    }

                    if (assertionToMessageArc.ArcRole.Equals("http://xbrl.org/arcrole/2010/assertion-unsatisfied-message"))
                    {
                        if (!evaluateResult)
                        {
                            string message = CompileMessage(this.MessageUnsatisfied, compiler, variableEntries, parameterDictionary, variableFallBack);
                            log4net.GlobalContext.Properties["satisfied"] = "no";
                            FormulaProcessor.log.Error(message);
                            dts.ValidatedFormula = false;
                            formulaProcessor.assertionsNotSatisfied++;
                        }
                    }
                }
            }
            else
            {
                if (evaluateResult)
                {
                    log4net.GlobalContext.Properties["satisfied"] = "yes";

                    string msg = " ";

                    //foreach (string variable in variableEntries.Keys)
                    //{
                    //    msg = msg + variable + " context:" + variableEntries[variable].ContextRefName + " ";
                    //}

                    FormulaProcessor.log.Info(this.ElementLabel + msg);
                    formulaProcessor.assertionsSatisfied++;
                }
                else
                {
                    log4net.GlobalContext.Properties["satisfied"] = "no";

                    string msg = " ";

                    foreach (string variable in variableEntries.Keys)
                    {
                        msg = msg + variable + " context:" + variableEntries[variable].ContextRefName + " ";
                    }

                    FormulaProcessor.log.Error(this.ElementLabel + msg);
                    dts.ValidatedFormula = false;
                    formulaProcessor.assertionsNotSatisfied++;
                }
            }

            if (this.ElementLabel == null && this.AssertionToMessageArcs.Count == 0)
            {
                if (evaluateResult)
                {
                    log4net.GlobalContext.Properties["satisfied"] = "yes";
                    FormulaProcessor.log.Info("This assertion evaluated true");
                    formulaProcessor.assertionsSatisfied++;
                }
                else
                {
                    log4net.GlobalContext.Properties["satisfied"] = "no";
                    FormulaProcessor.log.Error("This assertion evaluated false");
                    dts.ValidatedFormula = false;
                    formulaProcessor.assertionsNotSatisfied++;
                }
            }
        }
    }
}
